/*
 * Copyright (C) 2014 luke.
 * 
 * This file is part of Silvie.
 * 
 * Silvie is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Silvie is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Silvie.  If not, see <http://www.gnu.org/licenses/>.
 */

package silvie.gui.previewers;

import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.io.File;
import java.io.IOException;

import javax.media.j3d.Alpha;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3f;

import silvie.gui.Preview;
import utils.objwriter.OBJWriter;

import com.sun.j3d.utils.universe.SimpleUniverse;

/**
 * AbstractPreviewer specialization for 3D models using the Java3D library.
 * 
 * @author luke
 */
public class Previewer3D extends AbstractPreviewer {
	private static final String MODEL_FILTER_EXTENSION = "obj";
	private static final String MODEL_FILTER_DESCRIPTION = "OBJ files (*.obj)";
	private static final int CANVAS_PREFERRED_WIDTH = 640;
	private static final int CANVAS_PREFERRED_HEIGHT = 480;
	private static final int ROTATION_ALPHA_DURATION = 4000;
	private static final double ROTATOR_SPHERE_RADIUS = 1000.0;
	private static final int BACK_CLIP_DISTANCE = 1000;
	private static final float CAMERA_Z_POSITION = 1000.0f;
	private static final float MODEL_Y_POSITION = -200.0f;
	private SimpleUniverse universe;
	private Shape3D shape3d;

	/**
	 * Previewer3D constructor.
	 * 
	 * @param preview the Preview to display on.
	 * @param shape3d the Shape3D to display.
	 */
	public Previewer3D(Preview preview, Shape3D shape3d) {
		super(preview);
		this.universe = null;
		this.shape3d = shape3d;
		super.filterDescription = MODEL_FILTER_DESCRIPTION;
		super.filterExtension = MODEL_FILTER_EXTENSION;
	}

	/**
	 * Creates a scene graph for the 3D model, translates the model on the
	 * y-axis by {@code MODEL_Y_POSITION} and sets the rotation.
	 * 
	 * @return a BranchGroup for the 3D model.
	 */
	private BranchGroup createSceneGraph() {
		BranchGroup objRoot = new BranchGroup();
		TransformGroup objTrans = new TransformGroup();
		objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		objRoot.addChild(objTrans);
		// Move the shape down on the y-axis
		TransformGroup moveDownGroup = new TransformGroup();
		Transform3D moveDownTrans = new Transform3D();
		moveDownTrans.setTranslation(new Vector3f(0, MODEL_Y_POSITION, 0));
		moveDownGroup.setTransform(moveDownTrans);
		objTrans.addChild(moveDownGroup);
		moveDownGroup.addChild(this.shape3d);
		// Rotate the shape
		Transform3D yAxis = new Transform3D();
		Alpha rotationAlpha = new Alpha(-1, ROTATION_ALPHA_DURATION);
		RotationInterpolator rotator = new RotationInterpolator(rotationAlpha, objTrans, yAxis, 0, (float) Math.PI * 2.0f);
		BoundingSphere bounds = new BoundingSphere(new Point3d(0, 0, 0), ROTATOR_SPHERE_RADIUS);
		rotator.setSchedulingBounds(bounds);
		objRoot.addChild(rotator);
		objRoot.compile();
		return objRoot;
	}

	@Override
	public void doPreview() {
		GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
		Canvas3D canvas = new Canvas3D(config);
		canvas.setPreferredSize(new Dimension(CANVAS_PREFERRED_WIDTH, CANVAS_PREFERRED_HEIGHT));
		BranchGroup scene = createSceneGraph();
		this.universe = new SimpleUniverse(canvas);
		// Move the camera back on the z-axis
		Transform3D cameraTransform = new Transform3D();
		TransformGroup viewPlatformGroup = this.universe.getViewingPlatform().getViewPlatformTransform();
		cameraTransform.setTranslation(new Vector3f(0, 0, CAMERA_Z_POSITION));
		viewPlatformGroup.setTransform(cameraTransform);
		// Necessary to see the whole model
		this.universe.getViewer().getView().setBackClipDistance(BACK_CLIP_DISTANCE);
		this.universe.addBranchGraph(scene);
		super.preview.setCanvas(canvas, true);
	}

	@Override
	public void close() {
		this.universe.cleanup();
	}

	@Override
	public File doSave() throws IOException {
		OBJWriter writer = new OBJWriter(this.savedFile.getAbsolutePath());
		writer.writeNode(this.shape3d);
		// This will create the MTL and texture files in same directory
		writer.close();
		return this.savedFile;
	}
}
